package org.anderground.modules.editor.importers.pmz.map;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import org.anderground.core.Logger;
import org.anderground.core.model.City;
import org.anderground.core.model.Country;
import org.anderground.core.model.Line;
import org.anderground.core.model.ModelData;
import org.anderground.core.model.ModelException;
import org.anderground.core.model.Station;
import org.anderground.core.model.Transfer;
import org.anderground.core.model.Tunnel;
import org.anderground.core.model.ModelData.Builder;
import org.anderground.core.util.Coord;
import org.anderground.core.util.Point;
import org.anderground.modules.editor.importers.pmz.InvalidLineException;
import org.anderground.modules.editor.importers.pmz.LineTimeParsingException;
import org.anderground.modules.editor.importers.pmz.archive.EntrySection;
import org.anderground.modules.editor.importers.pmz.archive.PmzEntry;
import org.anderground.modules.editor.importers.pmz.archive.PmzEntryRecord;
import org.anderground.modules.editor.importers.pmz.archive.PmzFile;

public class PmzMetroMap {

	public static boolean MERGE_SIMILAR_STATIONS = false;
	// if lines with the same color is one line(this isn't so in new-york map,
	// for example)
	public static boolean MERGE_SIMILAR_LINES = false;
	public static boolean REMOVE_ZEROSIZE_TUNNELS = false;

	private List<PmzLine> lines = new ArrayList<PmzLine>();
	private String sityName = "Untitled city";
	private String country;
	private String[] aliases = new String[2];

	public PmzMetroMap(PmzFile pmz) {
		parse(pmz);
	}

	private void parse(PmzFile pmz) {
		PmzEntry entry = pmz.getEntry("Metro.trp");
		Enumeration<? extends EntrySection> e = entry.getSections();
		parseMapInfo(pmz);

		while (e.hasMoreElements()) {
			EntrySection section = e.nextElement();
			try {
				if (section.getName().startsWith("Line")) {
					EntrySection lineSection = section;
					String[] stations = lineSection.getValues("Stations");
					String lineName = lineSection.getValues("Name")[0];
					PmzEntry lineMapFile = pmz.getEntry("Metro.map");
					java.awt.Color color = getLineColor(lineName, lineMapFile);
					PmzLine line = null;

					Logger.i("Reading line: " + lineName);
					Coord[] coords = getCoordinates(lineName, lineMapFile);
					line = readPMLine(lineName, color, stations, coords);
					lines.add(line);

					if (coords.length != line.getStations().length)
						throw new InvalidLineException(
								"not enough corrdinatts for line: "
										+ line.getName());

					PmzLine.StationEntry[] sts = line.getStations();

					readTunnels(section, lineSection, sts);

					if (REMOVE_ZEROSIZE_TUNNELS)
						line.removeZeroSizeTunnels();
					line.completeTunnels();
					line.removeIsolatedStations();
					line.setStationNamesToAliases(lineSection
							.getValues("Aliases"));
				}
			} catch (InvalidLineException e1) {
				Logger
						.w("Line \""
								+ section.getValues("Name")[0]
								+ "\" no coordinats can be found; line will be ignored");

			}
		}
		if (MERGE_SIMILAR_LINES)
			mergeSimilarLines();

		createTransfers(entry);

	}

	private void parseMapInfo(PmzFile pmz) {
		Logger.i(pmz.getFileName().substring(0, pmz.getFileName().length() - 4)
				+ ".cty");
		EntrySection info = pmz.getEntry(
				pmz.getFileName().substring(0, pmz.getFileName().length() - 4)
						+ ".cty").getSection("Options");
		sityName = info.getValues("CityName")[0];
		country = info.getValues("Country")[0];
		aliases[0] = info.getValues("RusName")[0];
		aliases[1] = info.getValues("Name")[0];

	}

	private void mergeSimilarLines() {
		for (PmzLine line : lines) {
			for (PmzLine line2 : lines) {
				if (line.getLine().getColor() == line2.getLine().getColor()) {
					line2.setLine(line.getLine());
				}
			}
		}
	}

	// read times add create tunnels
	private void readTunnels(EntrySection section, EntrySection lineSection,
			PmzLine.StationEntry[] sts) {
		String drv[] = new String[sts.length];
		try {
			drv = lineSection.getValues("Driving");
			readTunnels(sts, drv);

		} catch (ArrayIndexOutOfBoundsException e1) {
			Logger.w("Line \"" + section.getValues("Name")[0]
					+ "\" diven times broken");

			Arrays.fill(drv, "-1");
			try {
				readTunnels(sts, drv);
			} catch (LineTimeParsingException e2) {
				e2.printStackTrace();
			}
		} catch (LineTimeParsingException e2) {
			Logger.w("Line \"" + section.getValues("Name")[0]
					+ "\" excess braket");

			drv = new String[sts.length * 2];
			Arrays.fill(drv, "-1");
			try {
				readTunnels(sts, drv);
			} catch (LineTimeParsingException e3) {
				e2.printStackTrace();
			}
		}
	}

	private Coord[] getCoordinates(String lineName, PmzEntry lineMapFile)
			throws InvalidLineException {
		Coord[] coords;
		coords = getCoords(getLineStringCoords(lineName, lineMapFile));
		return coords;
	}

	private String[] getLineStringCoords(String lineName, PmzEntry lineMapFile)
			throws InvalidLineException {
		String[] Xs;
		if (lineMapFile.getSection(lineName) != null)
			Xs = lineMapFile.getSection(lineName).getValues("Coordinates");
		else
			throw new InvalidLineException("no coordinats in Metro.map file");
		return Xs;
	}

	private java.awt.Color getLineColor(String lineName, PmzEntry lineMapFile) {
		java.awt.Color color;
		try {
			color = java.awt.Color.decode("#"
					+ lineMapFile.getSection(lineName).getValues("Color")[0]);
		} catch (Exception ex) {
			color = java.awt.Color.RED;
		}
		return color;
	}

	public void build(ModelData.Builder builder) {
		Hashtable<String, Station> stMap = new Hashtable<String, Station>();
		List<PmzLine.StationEntry> entries = new ArrayList<PmzLine.StationEntry>();
		builder.setCity(new City(sityName, Arrays.asList(aliases), new Country(
				"", country), null));
		try {
			for (PmzLine line : lines) {
				buildLineStations(builder, stMap, entries, line);
				buildLineTunnels(builder, stMap, line);

			}
			for (PmzLine.StationEntry e : entries)
				for (PmzLine.TransitionEntry te : e.transfers) {
					createTransfer(builder, stMap, te);
				}
		} catch (ModelException e) {
			e.printStackTrace();
		}
		clear(builder);
	}

	private void buildLineTunnels(ModelData.Builder builder,
			Hashtable<String, Station> stMap, PmzLine line)
			throws ModelException {
		for (PmzLine.StationEntry e : line.getStations()) {
			for (PmzLine.TransitionEntry te : e.tunnels) {
				builder.add(createTunnel(stMap, line, te));
			}

		}
	}

	private void buildLineStations(ModelData.Builder builder,
			Hashtable<String, Station> stMap,
			List<PmzLine.StationEntry> entries, PmzLine line)
			throws ModelException {
		for (PmzLine.StationEntry stationEntry : line.getStations()) {
			builder.add(createStation(stMap, entries, stationEntry));
		}
	}

	private void createTransfer(ModelData.Builder builder,
			Hashtable<String, Station> stMap, PmzLine.TransitionEntry te)
			throws ModelException {
		if (stMap.get(te.to) == null)
			Logger.w(te.to);
		if (stMap.get(te.from) != stMap.get(te.to)) {
			Transfer t = new Transfer(stMap.get(te.from), stMap.get(te.to),
					te.time);
			builder.add(t);
		}
	}

	private Tunnel createTunnel(Hashtable<String, Station> stMap, PmzLine line,
			PmzLine.TransitionEntry te) throws ModelException {
		String lineName = line.getName();
		Tunnel t = null;
		if (stMap.get(lineName + te.to) == null)
			Logger.w("tunnel desination station: " + te.to + "don't exist");
		else {
			t = createTunnel(stMap.get(lineName + te.from), stMap.get(lineName
					+ te.to), te.time, line.getLine());
		}
		return t;
	}

	private Station createStation(Hashtable<String, Station> stMap,
			List<PmzLine.StationEntry> entries, PmzLine.StationEntry e)
			throws ModelException {
		Station s = null;
		if (e.linkTo == null) {
			if (stMap.get(e.getLine().getName() + e.getName()) != null) {
				s = stMap.get(e.getLine().getName() + e.getName());
			} else {
				s = new Station(e.getPresentName(), new Coord(e.getX(), e
						.getY()), null, null, null, new Station.Label(0,
						new Point(0, 0), true));

			}

		}
		if (e.linkTo != null) {
			if (stMap.get(e.linkTo.getLine().getName() + e.linkTo.getName()) != null) {
				s = stMap
						.get(e.linkTo.getLine().getName() + e.linkTo.getName());

			} else {
				PmzLine.StationEntry g = e.linkTo;
				s = new Station(g.getPresentName(), new Coord(g.getX(), g
						.getY()), null, null, null, new Station.Label(0,
						new Point(0, 0), true));

				stMap.put(g.getLine().getName() + g.getName(), s);
			}
		}
		stMap.put(e.getLine().getName() + e.getName(), s);
		entries.add(e);
		return s;
	}

	// removes isolated stations that creates after linking
	private void clear(Builder builder) {
		List<Station> toReamove = new ArrayList<Station>();
		for (Station s : builder.getModelData().getStations()) {
			if (s.getTunnels().size() == 0)
				toReamove.add(s);
		}
		for (Station s : toReamove) {
			try {
				builder.remove(s);
			} catch (ModelException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	private Tunnel createTunnel(Station from, Station to, int time, Line line) {
		for (Tunnel t : from.getTunnels()) {
			if (t.getToStation() == to && t.getLine() == line)
				return t;
		}
		return new Tunnel(from, to, time, line, false);
	}

	// If u want to change something in this method - it m.b. easier to fully
	// rewrite it =)
	private PmzLine readPMLine(String lineName, Color color, String[] stations,
			Coord[] coords) {
		PmzLine line = new PmzLine(lineName, color, 0);
		String pre = null;
		int stCount = 0;
		for (int i = 0; i < stations.length; i++) {

			String name;
			if (stations[i].indexOf('(') != -1) { // if station have directly
													// named previouses and
													// nexts
				pre = null;
				name = stations[i].substring(0, stations[i].indexOf('('));
				ArrayList<String> nexts = new ArrayList<String>(), previouses = new ArrayList<String>();
				boolean noPriviousStationLeft = false;
				if (stations[i].indexOf(')') != -1) {
					nexts.add(stations[i].substring(
							stations[i].indexOf('(') + 1).substring(
							0,
							stations[i].substring(stations[i].indexOf('(') + 1)
									.length() - 1));
					noPriviousStationLeft = true;
				} else {
					nexts.add(stations[i]
							.substring(stations[i].indexOf('(') + 1));
					i++;
				}
				while (!noPriviousStationLeft && stations[i].indexOf(')') == -1) {
					previouses.add(stations[i]);
					if (stations[i + 1].indexOf(')') != -1) {
						noPriviousStationLeft = true;
						nexts.add(stations[i + 1].substring(0, stations[i + 1]
								.length() - 1));
						i++;
					} else {
						nexts.add(stations[i + 1]);
						i = i + 2;
					}

				}

				if (!noPriviousStationLeft)
					previouses.add(stations[i].substring(0, stations[i]
							.length() - 1));

				readStation(line, name, previouses.toArray(new String[0]),
						nexts.toArray(new String[0]), coords[stCount]);
				stCount++;
			} else {
				name = stations[i];
				readStation(line, name, pre, coords[stCount++]);
				pre = name;
			}

		}
		return line;
	}

	private Coord[] getCoords(String[] Xs) {
		Coord[] coords = new Coord[Xs.length / 2];
		for (int i = 0; i < Xs.length; i += 2) {

			int x = Integer.parseInt(Xs[i]);
			int y = Integer.parseInt(Xs[i + 1]);

			coords[i / 2] = new Coord(x, y);

		}
		return coords;
	}

	private void readStation(PmzLine line, String name, String[] prevs,
			String[] nexts, Coord coord) {
		line.addStation(name, prevs, nexts, coord);

	}

	private void readStation(PmzLine line, String name, String pre, Coord c) {
		line.addStation(name, pre, c);
	}

	private void readTunnels(PmzLine.StationEntry[] sts, String[] drv)
			throws LineTimeParsingException {
		int drvCount = 0;
		int indicator = (sts[sts.length - 1].isComplex()) ? 0 : 1;
		for (int i = 0; i < sts.length - indicator; i++) {
			PmzLine.StationEntry st = sts[i];
			int time = 0;

			if (st.isComplex()) {
				int size = st.getNextStations().length
						+ st.getPreviuosStations().length;
				if (drv[drvCount].indexOf('(') == -1)
					drvCount++;
				if (!drv[drvCount].equals("()")) {
					List<String> nextTimes = new ArrayList<String>(), prevTimes = new ArrayList<String>();

					int j;
					for (j = 0; j < size; j++) {
						// if next station
						if (j % 2 == 0) {
							nextTimes.add(drv[drvCount + j]);
						} else {
							prevTimes.add(drv[drvCount + j]);
						}

					}
					for (int k = 0; k < nextTimes.size(); k++) {
						if (!st.getNextStations()[k].trim().equals(""))
							st.addOutTunnelTime(st.getNextStations()[k],
									parseInt(nextTimes.get(k)));

					}
					for (int k = 0; k < prevTimes.size(); k++) {
						if (!st.getPreviuosStations()[k].trim().equals(""))
							st.addInTunnelTime(st.getPreviuosStations()[k],
									parseInt(prevTimes.get(k)));

					}
					drvCount += j;
				} else {
					drvCount++;
				}
			} else {

				if (!drv[drvCount].equals("()")) {
					if (drv[drvCount].indexOf('(') != -1
							|| drv[drvCount].indexOf(')') != -1) {
						Logger.w("Warning, smth wrong (excess braket)");
						throw new LineTimeParsingException();
					}
					time = parseFloatTime(drv[drvCount]);
				} else
					time = 0;

				st.setTimeToSingleNext(time);

				drvCount++;

			}
		}

	}

	private int parseFloatTime(String times) {
		int time;
		if (times.indexOf('.') != -1) {
			String min = times.substring(0, times.indexOf('.'));
			String s = times.substring(times.indexOf('.') + 1);
			time = Integer.parseInt(min) * 60 + Integer.parseInt(s);
		} else {
			time = Integer.parseInt(times) * 60;
		}
		return time;
	}

	private int parseInt(String string) {
		String str = string;
		int begin = str.indexOf('(');
		begin++;
		int end = str.indexOf(')');
		if (end == -1) {
			str = str.substring(begin);
		} else
			str = str.substring(begin, end);

		if (str.equals(""))
			return 0;
		else
			return parseFloatTime(str);
	}

	private void createTransfers(PmzEntry s) {
		EntrySection es = s.getSection("Transfers");

		String[] oneSide = new String[0];
		if (s.getSection("AdditionalInfo") != null) {
			String transfers[] = s.getSection("AdditionalInfo").getValues(
					"OnePlatformTransfers");
			oneSide = new String[transfers.length / 2];
			for (int i = 0; i < transfers.length; i += 2) {
				oneSide[i / 2] = transfers[i];
			}
		}
		if (es == null)
			return;
		for (PmzEntryRecord rec : es.getEntryRecords()) {
			if (Arrays.asList(oneSide).contains(rec.getName())) {
				mergeStations(rec.getValues());
			} else {
				parseTransfer(rec.getValues());
			}
		}

	}

	private void mergeStations(String[] values) {
		String fromLine = values[0];
		String fromStation = values[1];
		String toLine = values[2];
		String toStation = values[3];
		PmzLine.StationEntry from = getStationFromLine(fromLine, fromStation);
		PmzLine.StationEntry to = getStationFromLine(toLine, toStation);
		from.linkTo(to);
	}

	private void parseTransfer(String[] values) {
		if (values.length < 4)
			return;
		String fromLine = values[0];
		String fromStation = values[1];
		String toLine = values[2];
		String toStation = values[3];
		int straightTime = 0;
		int backTime = 0;
		if (values.length > 4) {
			straightTime = parseFloatTime(values[4]);
		}
		if (values.length > 5) {
			try {
				backTime = parseFloatTime(values[5]);
			} catch (Exception e) {
				backTime = straightTime;
			}

		} else
			backTime = straightTime;

		PmzLine.StationEntry from = getStationFromLine(fromLine, fromStation);
		PmzLine.StationEntry to = getStationFromLine(toLine, toStation);

		if (from == null || to == null) {
			Logger
					.w("can't create transfer:"
							+ ((from == null) ? fromStation : toStation)
							+ " not found");
			return;
		}

		if (MERGE_SIMILAR_STATIONS
				&& from.getPresentName().equals(to.getPresentName())) {
			mergeStations(values);
			return;
		}
		from.addTransfer(toLine + toStation, straightTime);
		to.addTransfer(fromLine + fromStation, backTime);

	}

	private PmzLine.StationEntry getStationFromLine(String line, String station) {
		for (PmzLine l : lines) {
			if (l.getName().equals(line)) {
				for (PmzLine.StationEntry entry : l.getStations()) {
					if (entry.getName().equals(station)) {
						return entry;
					}
				}
			}
		}
		return null;
	}

}
